package cn.imatu.framework.lock.aspect;

import cn.imatu.framework.core.utils.spring.SpringUtils;
import cn.imatu.framework.lock.annotation.DistributedLock;
import cn.imatu.framework.lock.exception.LockException;
import cn.imatu.framework.lock.manager.ILockManager;
import cn.imatu.framework.lock.model.ILock;
import cn.imatu.framework.tool.core.exception.Assert;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * redis分布式锁的切面
 */
@Aspect
@Component
public class DistributedLockAspect {
    private static final Logger log = LoggerFactory.getLogger(DistributedLockAspect.class);

    @Around(value = "@annotation(distributedLock)")
    public Object lock(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        Object output = null;
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        // 获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = localVariableTable.getParameterNames(method);
        // 使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        // SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < Objects.requireNonNull(paraNameArr).length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        String lockKey = distributedLock.lockKey();

        // 使用变量方式传入业务动态数据
        if (lockKey.matches("^#.*.$")) {
            lockKey = parser.parseExpression(lockKey).getValue(context, String.class);
        }
        String lockBeanName = distributedLock.beanName();
        long waitTime = distributedLock.waitTime();
        TimeUnit unit = distributedLock.unit();
        int lockFailCode = distributedLock.lockFailCode();
        String lockFailMessage = distributedLock.lockFailMessage();

        Assert.notEmpty(lockBeanName, "lock bean name is empty");
        ILockManager lockService = SpringUtils.getBean(lockBeanName, ILockManager.class);
        ILock lock = null;
        try {
            lock = lockService.getLock(lockKey);
            boolean isGetLock = lock.tryLock(waitTime, unit);
            if (!isGetLock) {
                throw new LockException(lockFailCode, lockFailMessage);
            }
            output = joinPoint.proceed();
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
        return output;
    }
}
